function Vue(options) {
  this._init(options);
}

Vue.prototype._init = function (options) {
  const vm = this;
  vm.$options = {
    propsData: options.propsData,
    props: options.props,
  };
  initState(vm);
};

function initState(vm) {
  const opts = vm.$options;
  if (opts.props) initProps(vm, opts.props);
}

function initProps(vm, propsOptions) {
  const propsData = vm.$options.propsData;
  const props = (vm._props = {});
  const keys = (vm.$options._propkeys = []);
  for (const key in propsOptions) {
    keys.push(key);
    const value = validateProp(key, propsOptions, propsData, vm);
    // 判断是否为开发环境
    if (process.env.NODE_ENV !== "production") {
      // 将camcelCase转为kebab-case的格式
      const hyphenatedKey = hyphenate(key);
      // 验证是否为vue中保留的属性
      if (["key", "ref", "slot", "slot-scope", "is"].includes(hyphenatedKey)) {
        console.warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`
        );
      }
      // 这里对prop进行直接修改时的警告
      defineReactive(props, key, value, () => {
        console.warn(
          `Avoid mutating a prop directly since the value will be ` +
          `overwritten whenever the parent component re-renders. ` +
          `Instead, use a data or computed property based on the prop's ` +
          `value. Prop being mutated: "${key}"`
        );
      });
    } else {
      // 在生产环境中，不做任何错误提示
      defineReactive(props, key, value);
    }
    if (!(key in vm)) {
      // 在vm上取key的值时，就从vm._props上取
      // 赋值给vm.key时，也同时赋值给vm._props上
      proxy(vm, `_props`, key);
    }
  }
}

const hyphenateRe = /\B([A-Z])/g;
function hyphenate(str) {
  return str.replace(hyphenateRe, "-$1").toLowerCase();
}

function validateProp(key, propsOptions, propsData, vm) {
  // prop定义
  const prop = propsOptions[key];
  // 是否有传这个prop，true表示没传
  const absent = !hasOwn(propsData, key);
  // prop的值
  let value = propsData[key];
  const booleanIndex = getTypeIndex(Boolean, prop.type);
  if (booleanIndex > -1) {
    // 如果没传prop值且prop定义中没有default
    if (absent && !hasOwn(prop, "default")) {
      value = false;
    } else if (value === "" || value === hyphenate(key)) {
      // 如果value值为空字符串或value与key相同
      // 则需要设置boolean为true
      // 但必须要在Boolean类型定义先于String类型才行
      // 也就是在prop.type为数组时Boolean要写在String之前
      // 或者未定义String类型
      // 此时，会设置value为true
      const stringIndex = getTypeIndex(String, prop.type);
      if (stringIndex < 0 || booleanIndex < stringIndex) {
        value = true;
      }
    }
  }
  if (value === undefined) {
    value = getPropDefaultValue(vm, prop, key);
    // 后对value值进行监听
    // observe(value)
  }
  // 开发环境下，对prop值做类型检查和提示
  if (process.env.NODE_ENV !== "production") {
    assertProp(prop, key, value, vm, absent);
  }
  return value;
}

function getTypeIndex(type, expectedTypes) {
  if (!Array.isArray(expectedTypes)) {
    return isSameType(expectedTypes, type) ? 0 : -1;
  }
  for (let i = 0, len = expectedTypes.length; i < len; i++) {
    if (isSameType(expectedTypes[i], type)) {
      return i;
    }
  }
  return -1;
}

function isSameType(a, b) {
  return getType(a) === getType(b);
}

const functionTypeCheckRE = /^\s*function (\w+)/;

function getType(fn) {
  const match = fn && fn.toString().match(functionTypeCheckRE);
  return match ? match[1] : "";
}

function getPropDefaultValue(vm, prop, key) {
  if (!hasOwn(prop, "default")) {
    return undefined;
  }
  const def = prop.default;
  // 如果prop.default为一个对象，则会给出一个警告
  // prop.default为对象类型值（包括数组时）
  // 需要使用一个返回该对象值的函数作为prop.default
  if (process.env.NODE_ENV !== "production" && isObject(def)) {
    console.warn(
      'Invalid default value for prop "' +
      key +
      '": ' +
      "Props with type Object/Array must use a factory function " +
      "to return the default value."
    );
  }
  // 当vm.$options.propsData[key]为undefined时
  // 即没有传这个prop值，或者是传的值为undefined（null不行）时
  // 而且vm._props[key]有值，说明之前已经获取过这个prop值了
  // 那么就直接使用vm._props[key]的值作为prop值
  if (
    vm &&
    vm.$options.propsData &&
    vm.$options.propsData[key] === undefined &&
    vm._props[key] !== undefined
  ) {
    return vm._props[key];
  }
  // 如果def为函数且prop.type的定义不是Function
  // 则调用prop函数且将结果作为prop的值
  // 否则将def作为prop的值
  return typeof def === "function" && getType(prop.type) !== "Function"
    ? def.call(vm)
    : def;
}

function assertProp(prop, name, value, vm, absent) {
  // 定义了必填但没传，则报错
  if (prop.required && absent) {
    console.warn('Missing required prop: "' + name + '"');
    return;
  }
  // value值为undefined或null，则不必继续校验类型是否正确
  if (value == null && !prop.required) {
    return;
  }
  let type = prop.type;
  let valid = !type || type === true;
  const expectedTypes = [];
  if (type) {
    if (!Array.isArray(type)) {
      type = [type];
    }
    for (let i = 0; i < type.length && !valid; i++) {
      const assertedType = assertType(value, type[i], vm);
      expectedTypes.push(assertedType.expectedType || "");
      valid = assertedType.valid;
    }
  }

  const hasExpectedTypes = expectedTypes.some((t) => t);
  if (!valid && hasExpectedTypes) {
    console.warn("类型不对");
    return;
  }
  const validator = prop.validator;
  if (validator) {
    if (!validator(value)) {
      console.warn(
        'Invalid prop: custom validator check failed for prop "' + name + '".'
      );
    }
  }
}

const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol|BigInt)$/;

function assertType(value, type, vm) {
  let valid;
  const expectedType = getType(type);
  // 判断是基本类型
  if (simpleCheckRE.test(expectedType)) {
    const t = typeof value;
    valid = t === expectedType.toLowerCase();
    if (!valid && t === "object") {
      valid = value instanceof type;
    }
  } else if (expectedType === "Object") {
    valid = isPlainObject(value);
  } else if (expectedType === "Array") {
    valid = Array.isArray(value);
  } else {
    try {
      valid = value instanceof type;
    } catch (e) {
      console.warn(
        'Invalid prop type: "' + String(type) + '" is not a constructor'
      );
    }
  }

  return {
    valid,
    expectedType,
  };
}

const _toString = Object.prototype.toString;
function isPlainObject(value) {
  return _toString.call(value) === "[object Object]";
}

// 简易版的简易版defineReactive
function defineReactive(obj, key, val, customSetter) {
  Object.defineProperty(obj, key, {
    get() {
      return val;
    },
    set(newVal) {
      if (process.env.NODE_ENV !== "production" && customSetter) {
        // 当直接prop的值时，报错的方法就是在这里调用的
        // 当然如果是修改prop值的属性的值时，是不会触发这个setter的
        // 也就不会触发这个报错提示
        customSetter();
      }
      val = newVal;
    },
  });
}

// 这个配置可以共享
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop,
};

// noop就是个空函数
function noop() { }

function proxy(target, sourceKey, key) {
  sharedPropertyDefinition.get = function () {
    return target[sourceKey][key];
  };
  sharedPropertyDefinition.set = function (value) {
    target[sourceKey][key] = value;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return hasOwnProperty.call(obj, key);
}

function isObject(obj) {
  return typeof obj === 'object';
}
